/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package net.worlds.object;

import java.awt.Color;
import java.io.IOException;
import net.worlds.WorldsException;
import net.worlds.Chat;
import net.worlds.Main;
import net.worlds.PropChangeHandler;
import net.worlds.Util;
import net.worlds.lua.Event;
import net.worlds.ui.PartTreeNode;
import se.krka.kahlua.vm.JavaFunction;
import se.krka.kahlua.vm.LuaCallFrame;
import se.krka.kahlua.vm.LuaClosure;
import se.krka.kahlua.vm.LuaState;
import se.krka.kahlua.vm.LuaTableImpl;


public class Root extends GameObject{
    public Root() throws WorldsException
    {
        super("Root");
        addFunction("editor", new JavaFunction() {

            @Override
            public int call(LuaCallFrame callFrame, int nArguments) {
                callFrame.push(world.game.getEditorObject());
                return 1;
            }
        });
        addFunction("setType", new JavaFunction()
        {

            public int call(LuaCallFrame callFrame, int nArguments) {
                if(nArguments != 2 || !Util.isDouble(callFrame.get(1).toString()))
                    throw new RuntimeException("setType() requires an integer argument");
                double d = (Double) callFrame.get(1);
                Main.getGame().type = (int) d;
                if(Main.isEditor())
                    Main.getEditorTop().setSideScrollingButtons(d == 0.0);
                return 0;
            }
            
        });
        addFunction("getType", new JavaFunction()
        {
            public int call(LuaCallFrame callFrame, int nArguments) {
                callFrame.push(Main.getGame().type);
                return 1;
            }
        });
        addFunction("getWorld", new JavaFunction()
        {
            public int call(LuaCallFrame callFrame, int nArguments) {
                if(nArguments != 2 || !(callFrame.get(1) instanceof String))
                    throw new RuntimeException("getWorld() requires a string argument");
                String name = callFrame.get(1).toString();
                if(Main.getGame().getWorlds().containsKey(name))
                    callFrame.push(Main.getGame().getWorld(name).getRootObject());
                else
                    throw new RuntimeException("World "+name+" does not exist");
                return 1;
            }
        });
        addFunction("getService", new JavaFunction()
        {

            public int call(LuaCallFrame callFrame, int nArguments) {
                if(nArguments != 2 || !(callFrame.get(1) instanceof String))
                    throw new RuntimeException("getService() requires a string argument");
                try {
                    callFrame.push(world.getService((String) callFrame.get(1)));
                    return 1;
                } catch (WorldsException ex) {
                    throw new RuntimeException(ex);
                }
            }

        });
        addFunction("get", new JavaFunction()
        {

            public int call(LuaCallFrame callFrame, int nArguments) {
                if(nArguments != 2 || !Util.isDouble(callFrame.get(1).toString()))
                    throw new RuntimeException("get() requires an integer argument");
                double d = (Double) callFrame.get(1);
                callFrame.push(world.game.getByID((int) d));
                return 1;
            }
        });
        addFunction("scheduleTask", new JavaFunction()
        {
            public int call(final LuaCallFrame callFrame, int nArguments) {
                if(nArguments != 2)
                    throw new RuntimeException("schedule() requires 1 argument");
                if(!(callFrame.get(1) instanceof LuaClosure))
                    throw new RuntimeException("schedule() requires a function argument");
                final LuaClosure f = (LuaClosure)callFrame.get(1);
                Main.runInMainThread(new Runnable()
                {
                    public void run() {
                        LuaState state = Main.getScriptContext().newState(world);
                        state.setEnvironment(callFrame.getEnvironment());
                        try
                        {
                            state.call(f, null);
                        }
                        catch(Exception ex)
                        {
                            String line = callFrame.thread.stackTrace.split("\\n")[0];
                            String[] parts = line.split(":");
                            if(parts.length == 2)
                                line = parts[1];
                            String start = "task:"+line+": ";
                            if(ex instanceof RuntimeException && ex.getMessage() != null)
                                Main.log(start+ex.getMessage());
                            else
                                Main.log(start+ex.toString());
                            if(!(ex instanceof RuntimeException) || ex instanceof NullPointerException || Main.debug)
                            {
                                ex.printStackTrace();
                            }
                        }
                    }
                });
                return 0;
            }
        });
        addFunction("asyncTask", new JavaFunction()
        {
            public int call(final LuaCallFrame callFrame, int nArguments) {
                if(nArguments != 2)
                    throw new RuntimeException("schedule() requires 1 argument");
                if(!(callFrame.get(1) instanceof LuaClosure))
                    throw new RuntimeException("schedule() requires a function argument");
                final LuaClosure f = (LuaClosure)callFrame.get(1);
                Main.getScriptContext().run(new Runnable()
                {
                    public void run() {
                        LuaState state = Main.getScriptContext().newState(world);
                        state.setEnvironment(callFrame.getEnvironment());
                        try
                        {
                            state.call(f, null);
                        }
                        catch(Exception ex)
                        {
                            String line = callFrame.thread.stackTrace.split("\\n")[0];
                            String[] parts = line.split(":");
                            if(parts.length == 2)
                                line = parts[1];
                            String start = "task:"+line+": ";
                            if(ex instanceof RuntimeException && ex.getMessage() != null)
                                Main.log(start+ex.getMessage());
                            else
                                Main.log(start+ex.toString());
                            if(!(ex instanceof RuntimeException) || ex instanceof NullPointerException || Main.debug)
                            {
                                ex.printStackTrace();
                            }
                        }
                    }
                });
                return 0;
            }
        });
        addFunction("load", new JavaFunction()
        {

            public int call(LuaCallFrame callFrame, int nArguments) {
                if(nArguments != 2 || !Util.isDouble(callFrame.get(1).toString()))
                    throw new RuntimeException("load() requires an integer argument");
                double d = (Double) callFrame.get(1);
                try {
                    Main.loadFromID((int) d);
                } catch (IOException ex) {
                    throw new RuntimeException(ex);
                }
                return 0;
            }
        });
        addFunction("message", new JavaFunction()
        {

            public int call(LuaCallFrame callFrame, int nArguments) {
                if(nArguments == 1)
                {
                    world.setMessage(null);
                    if(Main.isServer())
                    {
                        Main.getServer().sendMessage("");
                    }
                    return 0;
                }
                else if(nArguments != 2 || !(callFrame.get(1) instanceof String))
                    throw new RuntimeException("Argument to message() must be a string");
                final String s = (String) callFrame.get(1);
                if(Main.isServer())
                {
                    Main.getServer().sendMessage(s);
                }
                Main.runInMainThread(new Runnable()
                {
                    public void run() {
                        world.setMessage(s);
                    }
                });
                return 0;
            }
        });
        addFunction("chat", new JavaFunction()
        {

            public int call(final LuaCallFrame callFrame, int nArguments) {
                if(nArguments != 3 || !(callFrame.get(1) instanceof Color) || !(callFrame.get(2) instanceof String))
                {
                    throw new RuntimeException("game:chat(color, message)");
                }
                final Color color = (Color) callFrame.get(1);
                final String msg = (String)callFrame.get(2);
                if(Main.isServer())
                {
                    Main.getServer().broadcastChat(color, msg);
                }
                else
                {
                    Chat.addChat(color, msg);
                }
                return 0;
            }

        });
        addFunction("getObjectsByType", new JavaFunction()
        {
            public int call(LuaCallFrame callFrame, int nArguments) {
                int idx = 1;
                if(nArguments != 2)
                    throw new RuntimeException("getObjectsByType() requires a string argument");
                String type = callFrame.get(1).toString();
                Class clazz;
                try
                {
                    clazz = Class.forName("net.worlds.object."+type);
                }
                catch(ClassNotFoundException ex)
                {
                    throw new RuntimeException(type+" is not a known object type");
                }
                LuaTableImpl table = world.game.getObjectsByType(clazz);
                callFrame.push(table);
                return 1;
            }
        });
        addFunction("getAllPlayers", new JavaFunction()
        {
            public int call(LuaCallFrame callFrame, int nArguments) {
                LuaTableImpl table = world.game.getObjectsByType(Player.class);
                callFrame.push(table);
                return 1;
            }
        });
        addFunction("play", new JavaFunction()
        {
            public int call(LuaCallFrame callFrame, int nArguments) {
                Main.getGame().play(true);
                return 0;
            }
        });
        addPropChangeHandler("CurrentCamera", new PropChangeHandler()
        {
            @Override
            public void handle(Object oldValue, Object value) throws WorldsException {
                if(value instanceof Camera)
                {
                    Main.getGame().currentCamera = (Camera) value;
                    if(!Main.noView)
                        Main.getView().setViewPropertiesFromCamera((Camera) value);
                }
            }
        });
        addEvent("Stopped", new Event(), "none");
        addEvent("Started", new Event(), "none");
        //x_;
    }
    @Override
    public Object getProp(String key) throws WorldsException
    {
        if(key.equals("CurrentCamera"))
            return Main.getGame().currentCamera;
        else
            return super.getProp(key);
    }
    @Override
    public void destructor()
    {
        
    }
@Override
    public void addChild(GameObject c) throws WorldsException
    {
        super.addChild(c);
        if(c instanceof Environment)
            world.environment = (Environment) c;
    }
    @Override
    public void removeChild(GameObject c)
    {
        super.removeChild(c);
        if(c instanceof Environment)
            world.environment = null;
    }
    @Override
    public void addToWorld() throws WorldsException
    {
        super.addToWorld();
        treeNode = new PartTreeNode(this);
        //if(UI.guiEnabled && (Main.getGame() == null || Main.getGame().getCurrentWorld() == null || world == Main.getGame().getCurrentWorld()))
        //    Main.getView().getMainFrame().getPartsTree().create(treeNode);
    }
}
